﻿
using System.IO;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO.Compression;
using System.Runtime.InteropServices;

namespace MethodSerializer
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            byte[] BA = MethodSerializer.SerializeMethod(typeof(Program).GetMethod("Consolewrite"));
            GenericDelegate MDel = MethodSerializer.DeserializeMethod<GenericDelegate>(BA);
            MDel("Text From Parameters ...");
            MethodSerializer.InvokeDeserializeMethod(BA, null, new object[] { "Text From Parameters with Invoke ..." });
            Console.ReadLine();
        }

        public delegate void GenericDelegate(string String_);
        public unsafe static void Consolewrite(string String_)
        {
            string Blubber = "Text From var ...";
            Console.WriteLine("Text From Method ...");
            Console.WriteLine(GetNewText());
            Console.WriteLine(String_);
            Console.WriteLine(Blubber);
            Console.WriteLine();
        }

        public static string GetNewText()
        {
            return "Text From Extern Method ...";
        }
    }
    public sealed class MethodSerializer
    {
        public static byte[] SerializeMethod(MethodInfo MethodInfo)
        {
            MethodBody MethBod = MethodInfo.GetMethodBody();
            MethodStorager MethStore = new MethodStorager();

            if (!MethodInfo.IsStatic)
                throw new Exception("Non Static Method .");

            MethStore.ReturnTyp = MethodInfo.ReturnType;
            MethStore.ILCode = MethBod.GetILAsByteArray();
            MethStore.MaxStackSize = MethBod.MaxStackSize;
            MethStore.InitLocals = MethBod.InitLocals;

            ParameterInfo[] ParameterInfos = MethodInfo.GetParameters();
            Type[] ParameterTypesS = new Type[ParameterInfos.Length];
            for (int i = 0; i < ParameterInfos.Length; i++)
            {
                ParameterTypesS[i] = ParameterInfos[i].ParameterType;
            }
            MethStore.ParameterInfos = ParameterTypesS;

            IDictionary<int, LocalVariable> locals = new SortedList<int, LocalVariable>();

            foreach (LocalVariableInfo localInfo in MethBod.LocalVariables)
            {
                locals.Add(localInfo.LocalIndex, new LocalVariable(localInfo.IsPinned, localInfo.LocalType.TypeHandle));
            }

            MethStore.LocalVars = locals;

            IlReader reader = new IlReader(MethStore.ILCode, MethodInfo.Module);
            MethStore.Fields = reader.Fields;
            MethStore.Methods = reader.Methods;
            MethStore.Types = reader.Types;
            MethStore.Strings = reader.Strings;

            return SerializeObject(MethStore);
        }
        public static T DeserializeMethod<T>(byte[] SerializedMethodBytes)
        {
            return (T)(object)(DeserializeMethodT(SerializedMethodBytes).CreateDelegate(typeof(T)));
        }
        public static object InvokeDeserializeMethod(byte[] SerializedMethodBytes, object obj, object[] Parameters)
        {
            return (DeserializeMethodT(SerializedMethodBytes).Invoke(obj, Parameters));
        }
        private static DynamicMethod DeserializeMethodT(byte[] SerializedMethodBytes)
        {
            MethodStorager MethStore = DeSerializeObject(SerializedMethodBytes);

            DynamicMethod DynamicMethod = new DynamicMethod(String.Concat("Method_", Guid.NewGuid().ToString().Replace("-", String.Empty)), MethStore.ReturnTyp, MethStore.ParameterInfos, true);

            DynamicILInfo ILInfo = DynamicMethod.GetDynamicILInfo();

            IlTokenResolver tokenResolver = new IlTokenResolver(MethStore.Fields, MethStore.Methods, MethStore.Types, MethStore.Strings);

            MethStore.ILCode = tokenResolver.ResolveCodeTokens(MethStore.ILCode, ILInfo);

            ILInfo.SetCode(MethStore.ILCode, MethStore.MaxStackSize);

            SignatureHelper locals = SignatureHelper.GetLocalVarSigHelper();
            foreach (int localIndex in MethStore.LocalVars.Keys)
            {
                LocalVariable localVar = MethStore.LocalVars[localIndex];
                locals.AddArgument(Type.GetTypeFromHandle(localVar.LocalType), localVar.IsPinned);
            }

            ILInfo.SetLocalSignature(locals.GetSignature());

            DynamicMethod.InitLocals = MethStore.InitLocals;

            return DynamicMethod;
        }
        private static byte[] SerializeObject(MethodStorager Obj)
        {
            BinaryFormatter BinFor = new BinaryFormatter();
            byte[] Result = null;
            using (MemoryStream MemStr = new MemoryStream())
            {
                BinFor.Serialize(MemStr, Obj);
                Result = MemStr.ToArray();
            }
            return Result;
        }
        private static MethodStorager DeSerializeObject(byte[] obj)
        {
            BinaryFormatter BinFor = new BinaryFormatter();
            MethodStorager Result = null;
            using (MemoryStream MemStr = new MemoryStream(obj))
            {
                Result = (MethodStorager)BinFor.Deserialize(MemStr);
            }
            return Result;
        }
        private class IlTokenResolver
        {
            private IDictionary<int, RuntimeFieldHandle> m_fields;
            private IDictionary<int, MethodBase> m_methods;
            private IDictionary<int, RuntimeTypeHandle> m_types;
            private IDictionary<int, string> m_Strings;
            public IlTokenResolver(IDictionary<int, RuntimeFieldHandle> fields, IDictionary<int, MethodBase> methods, IDictionary<int, RuntimeTypeHandle> types, IDictionary<int, string> Strings)
            {
                m_fields = fields;
                m_methods = methods;
                m_types = types;
                m_Strings = Strings;
            }
            public byte[] ResolveCodeTokens(byte[] code, DynamicILInfo dynamicInfo)
            {
                byte[] resolvedCode = new byte[code.Length];

                //copy code bytes
                Array.Copy(code, resolvedCode, code.Length);

                //resolve fields
                foreach (int offset in m_fields.Keys)
                {
                    int newMetadataToken = dynamicInfo.GetTokenFor(m_fields[offset]);

                    OverwriteInt32(resolvedCode, offset, newMetadataToken);
                }

                //resolve methods
                foreach (int offset in m_methods.Keys)
                {
                    int newMetadataToken;
                    MethodBase methodBase;

                    methodBase = m_methods[offset];

                    //generic types require the declaring type when resolving
                    if (methodBase.DeclaringType != null && methodBase.DeclaringType.IsGenericType)
                    {
                        newMetadataToken = dynamicInfo.GetTokenFor(methodBase.MethodHandle, methodBase.DeclaringType.TypeHandle);
                    }
                    else
                    {
                        newMetadataToken = dynamicInfo.GetTokenFor(methodBase.MethodHandle);
                    }

                    OverwriteInt32(resolvedCode, offset, newMetadataToken);
                }

                //resolve types
                foreach (int offset in m_types.Keys)
                {
                    int newMetadataToken = dynamicInfo.GetTokenFor(m_types[offset]);

                    OverwriteInt32(resolvedCode, offset, newMetadataToken);
                }

                //resolve strings
                foreach (int offset in m_Strings.Keys)
                {
                    int newMetadataToken = dynamicInfo.GetTokenFor(m_Strings[offset]);

                    OverwriteInt32(resolvedCode, offset, newMetadataToken);
                }

                return resolvedCode;
            }
            private void OverwriteInt32(byte[] code, int offset, int tokenValue)
            {
                code[offset++] = (byte)tokenValue;
                code[offset++] = (byte)(tokenValue >> 8);
                code[offset++] = (byte)(tokenValue >> 16);
                code[offset] = (byte)(tokenValue >> 24);
            }
        }
        private class IlReader
        {
            private static OpCode[] m_multiByteOpCodes;
            private static OpCode[] m_singleByteOpCodes;

            //token offset dictionaries
            private IDictionary<int, RuntimeFieldHandle> m_fields = new Dictionary<int, RuntimeFieldHandle>();
            private IDictionary<int, MethodBase> m_methods = new Dictionary<int, MethodBase>();
            private IDictionary<int, RuntimeTypeHandle> m_types = new Dictionary<int, RuntimeTypeHandle>();
            private IDictionary<int, string> m_Strings = new Dictionary<int, string>();

            static IlReader()
            {
                LoadOpCodes();
            }

            public IlReader(byte[] code, Module module)
            {
                ReadCode(code, module);
            }

            #region Code From Other Articles Refactored

            private static void LoadOpCodes()
            {
                m_singleByteOpCodes = new OpCode[0x100];
                m_multiByteOpCodes = new OpCode[0x100];
                FieldInfo[] opCodeFields = typeof(OpCodes).GetFields();
                for (int fieldIndex = 0; fieldIndex < opCodeFields.Length; fieldIndex++)
                {
                    FieldInfo opCodeField = opCodeFields[fieldIndex];
                    if (opCodeField.FieldType == typeof(OpCode))
                    {
                        OpCode opCode = (OpCode)opCodeField.GetValue(null);
                        ushort opCodeValue = (ushort)opCode.Value;
                        if (opCodeValue < 0x100)
                        {
                            m_singleByteOpCodes[(int)opCodeValue] = opCode;
                        }
                        else
                        {
                            m_multiByteOpCodes[opCodeValue & 0xff] = opCode;
                        }
                    }
                }
            }

            private void ReadCode(byte[] code, Module module)
            {
                int byteIndex = 0;
                while (byteIndex < code.Length)
                {
                    OpCode opCode;

                    ushort opValue = code[byteIndex++];

                    if (opValue != 0xfe)
                    {
                        opCode = m_singleByteOpCodes[(int)opValue];
                    }
                    else
                    {
                        opValue = code[byteIndex++];
                        opCode = m_multiByteOpCodes[(int)opValue];
                    }

                    int metadataToken;
                    int startingIndex = byteIndex;

                    switch (opCode.OperandType)
                    {
                        case OperandType.InlineField:
                            metadataToken = ReadInt32(code, ref byteIndex);
                            m_fields.Add(startingIndex, module.ResolveField(metadataToken).FieldHandle);
                            break;
                        case OperandType.InlineMethod:
                            metadataToken = ReadInt32(code, ref byteIndex);
                            m_methods.Add(startingIndex, module.ResolveMethod(metadataToken));
                            break;
                        case OperandType.InlineType:
                            metadataToken = ReadInt32(code, ref byteIndex);
                            m_types.Add(startingIndex, module.ResolveType(metadataToken).TypeHandle);
                            break;
                        case OperandType.InlineString:
                            metadataToken = ReadInt32(code, ref byteIndex);
                            m_Strings.Add(startingIndex, module.ResolveString(metadataToken));
                            break;
                        case OperandType.InlineBrTarget:
                            byteIndex = byteIndex + 4;
                            break;
                        case OperandType.InlineSig:
                            byteIndex = byteIndex + 4;
                            break;
                        case OperandType.InlineTok:
                            metadataToken = ReadInt32(code, ref byteIndex);

                            MemberInfo memberInfo = module.ResolveMember(metadataToken);

                            if (memberInfo.MemberType == MemberTypes.TypeInfo || memberInfo.MemberType == MemberTypes.NestedType)
                            {
                                Type type = memberInfo as Type;
                                m_types.Add(startingIndex, type.TypeHandle);
                            }
                            else if (memberInfo.MemberType == MemberTypes.Method || memberInfo.MemberType == MemberTypes.Constructor)
                            {
                                MethodBase m = memberInfo as MethodBase;
                                m_methods.Add(startingIndex, m);
                            }
                            else if (memberInfo.MemberType == MemberTypes.Field)
                            {
                                FieldInfo f = memberInfo as FieldInfo;
                                m_fields.Add(startingIndex, f.FieldHandle);
                            }

                            break;
                        case OperandType.InlineI:
                            byteIndex = byteIndex + 4;
                            break;
                        case OperandType.InlineI8:
                            byteIndex = byteIndex + 8;
                            break;
                        case OperandType.InlineR:
                            byteIndex = byteIndex + 8;
                            break;
                        case OperandType.InlineSwitch:
                            int count = ReadInt32(code, ref byteIndex);
                            byteIndex = byteIndex + (count * 4);
                            break;
                        case OperandType.InlineVar:
                            byteIndex = byteIndex + 2;
                            break;
                        case OperandType.ShortInlineBrTarget:
                            byteIndex = byteIndex + 1;
                            break;
                        case OperandType.ShortInlineI:
                            byteIndex = byteIndex + 1;
                            break;
                        case OperandType.ShortInlineR:
                            byteIndex = byteIndex + 4;
                            break;
                        case OperandType.ShortInlineVar:
                            byteIndex = byteIndex + 1;
                            break;
                    }

                }
            }

            private int ReadInt32(byte[] code, ref int byteIndex)
            {
                return (((code[byteIndex++] | (code[byteIndex++] << 8)) | (code[byteIndex++] << 0x10)) | (code[byteIndex++] << 0x18));
            }

            #endregion

            public IDictionary<int, RuntimeFieldHandle> Fields
            {
                get { return m_fields; }
            }

            public IDictionary<int, MethodBase> Methods
            {
                get { return m_methods; }
            }

            public IDictionary<int, RuntimeTypeHandle> Types
            {
                get { return m_types; }
            }

            public IDictionary<int, string> Strings
            {
                get { return m_Strings; }
            }
        }
        [Serializable]
        private sealed class MethodStorager
        {
            public int MaxStackSize;
            public bool InitLocals;
            public Type ReturnTyp;
            public Type[] ParameterInfos;
            public byte[] ILCode;
            public IDictionary<int, RuntimeFieldHandle> Fields;
            public IDictionary<int, MethodBase> Methods;
            public IDictionary<int, RuntimeTypeHandle> Types;
            public IDictionary<int, string> Strings;
            public IDictionary<int, LocalVariable> LocalVars;
        }
        [Serializable]
        public class LocalVariable
        {
            public bool IsPinned;
            public RuntimeTypeHandle LocalType;
            public LocalVariable(bool isPinned, RuntimeTypeHandle localType)
            {
                IsPinned = isPinned;
                LocalType = localType;
            }
        }
    }
}
